/**
 * Copyright 2021 xpstem.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.xpstem.lelink.connection;

import com.xpstem.lelink.command.EV3Command;
import com.xpstem.lelink.command.EV3ReplyCallback;
import com.xpstem.lelink.command.EV3CommandPackage;
import com.xpstem.lelink.command.EV3ReplyPackage;
import com.xpstem.lelink.enums.CommandType;
import com.xpstem.lelink.type.Data16;
import com.xpstem.lelink.util.StrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * connect base class.
 *
 * @author billy zhang
 */
public abstract class BaseConnection implements Connection {

    private static final Logger log = LoggerFactory.getLogger(BaseConnection.class);

    private static AtomicInteger counter = new AtomicInteger(1);
    private Map<Integer, EV3Command> commandMap = new HashMap<>();
    private Map<Integer, Reply> replyMap = new HashMap<>();

    @Override
    public void send(EV3Command command) throws IOException {
        send(command, null);
    }

    @Override
    public void send(EV3Command command, EV3ReplyCallback callback) throws IOException {
        int count = counter.getAndIncrement();
        commandMap.put(count, command);

        EV3CommandPackage pkg = command.getMessage();
        pkg.setCounter(count);

        byte[] data = pkg.getPackage();
        String hexStr = StrUtil.toHexWithSep(data, "_");
        String cmdDescr = command.description();
        log.info("{}, request data: {}", cmdDescr, hexStr);

        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        Data16.from(data.length).writeTo(outStream);
        outStream.write(data);

        boolean waitReply = callback == null && (command.getType() == CommandType.SYSTEM_COMMAND_REPLY ||
                command.getType() == CommandType.DIRECT_COMMAND_REPLY);

        Reply reply = new Reply(count, callback);
        replyMap.put(count, reply);
        try {
            doSend(outStream.toByteArray());

            if (!waitReply) {
                return;
            }

            try {
                // 等待10秒。
                reply.await(10);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
                Thread.currentThread().interrupt();
            }

            if (reply.getData() == null) {
                // 等待超时了，
                throw new IOException("no reply message.");
            }

            response(reply);

        } finally {
            replyMap.remove(counter);
        }
    }

    /**
     * 接收到数据后调用。
     *
     * @param counter
     * @param data
     */
    protected void response(int counter, byte[] data) {
        if (replyMap.containsKey(counter)) {
            replyMap.get(counter).response(data);
        }
    }

    /**
     * 同步处理回复。
     * 如发送命令时指定了callback, 则不会调用此方法，由callback异步处理回复。
     *
     * @param reply
     */
    protected void response(Reply reply) {

        try {
            String hexStr = StrUtil.toHexWithSep(reply.getData(), "_");
            if (commandMap.containsKey(reply.getCounter())) {
                EV3Command cmd = commandMap.get(reply.getCounter());
                String cmdDescr = cmd.description();
                log.info("{} response data: {}", cmdDescr, hexStr);
                EV3ReplyPackage replyPackage = new EV3ReplyPackage(reply.getCounter(), reply.getData());
                cmd.response(replyPackage);
            } else {
                log.warn("don't found command response data from brick: {}", hexStr);
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public void connect() {
        try {
            int state = doConnect();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    protected abstract int doConnect() throws IOException;

    protected abstract void doSend(byte[] data) throws IOException;

    @Override
    public abstract void disconnect() throws IOException;

}
